﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;

namespace Isuka.Collections
{
    public static class ByteArrayExtensions
    {
        public static readonly char[] Base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ23456789".ToCharArray();

        public static string ToBase64String(this byte[] source)
        {
            Contract.Requires(source != null);
            return Convert.ToBase64String(source);
        }

        public static char[] ToBase32CharArray(this byte[] source)
        {
            Contract.Requires(source != null);
            Contract.Ensures(Contract.Result<char[]>() != null);

            return source.ToNbitValues(5).Select(x => Base32Chars[x]).ToArray();
        }

        /// <summary>
        /// 入力バイト列を1～8ビットの整数値の列として返す
        /// Nbitで割り切れる長さになるように末尾に0を追加する
        /// </summary>
        /// <param name="source">入力ソース</param>
        /// <param name="n">区切り桁数。1～8</param>
        /// <returns></returns>
        public static IEnumerable<byte> ToNbitValues(this byte[] source, int n)
        {
            Contract.Requires<ArgumentOutOfRangeException>(0 < n && n <= 8, "n");
            Contract.Requires<ArgumentNullException>(source != null, "source");
            Contract.Ensures(Contract.Result<IEnumerable<byte>>() != null);

            return ToNbitValuesIterator(source, n);
        }

        static IEnumerable<byte> ToNbitValuesIterator(byte[] source, int n)
        {
            if (source.Length > 0)
            {
                var x = source.Length;
                var d = (x * 8) / n;
                var o = (x * 8) % n;
                for (int i = 0; i < d; i += n)
                {
                    var ix = i / 8;
                    var ox = i % 8;
                    if (ox == 0)
                    {
                        var bm = (byte)(0xFF << (8 - n));
                        yield return (byte)((source[ix] & bm) >> (8 - n));
                    }
                    else
                    {
                        var abm = (byte)(0xFF >> (8 - ox));
                        var bbm = (byte)(0xFF << (8 - n + ox));
                        yield return (byte)(((source[ix] & abm) << ox) | ((source[ix + 1] & bbm) >> (n - ox)));
                    }
                }
                if (o != 0)
                {
                    var bm = (byte)(0xFF >> (8 - o));
                    yield return (byte)((source[source.Length - 1] & bm) << o);
                }
            }
        }

    }
}
